type RangeItemParam = [number, number];

export default class RangeList {
  /**
   * 记录「子范围」数据，升序排序
   */
  private rangeList: RangeItemParam[] = [];

  private isValidRange(range: RangeItemParam) {
    if (!Array.isArray(range) || range.length !== 2 || isNaN(+[0]) || isNaN(range[1])) {
      throw "入参错误：需要为数组，长度为2，元素类型为number 。";
    }
  }

  /**
   * 添加「子范围」,
   * 遍历已知「子范围」，
   * 1. 若新添加「子范围」不覆盖于任意已知「子范围」，寻找合适位置直接追加
   * 2. 或根据重合情况，执行操作
   */
  public add(range: RangeItemParam) {
    this.isValidRange(range);

    let [start, end] = range;
    if (start === end) return;
    let { rangeList: _list } = this;
    if (!_list.length) _list.push(range);
    else {
      for (let i = 0; i < _list.length; i++) {
        let [_itemStart, _itemEnd] = _list[i];
        if (_itemStart > end) break;
        if (_itemStart <= start && _itemEnd >= end) return;
        if (start <= _itemStart && _itemStart < end && end <= _itemEnd) return this.mergeRangeByTop(range, i);
        if (start <= _itemStart && end >= _itemEnd) return this.mergeRangeByInclude(range, i);
        if (start >= _itemStart && start < _itemEnd && end >= _itemEnd) return this.mergeRangeByBottom(range, i);
      }
      /**
       * 寻找「子范围」插入位置：新添子范围尾部小于已知「子范围」首部，
       * 若没有符合条件的已知「子范围」，则新「子范围」追加到结尾。
       */
      let tempList = _list.map(([, end]) => end).reverse();
      let lastMatchIndex = tempList.findIndex(item => item < start);
      if (lastMatchIndex === -1) _list.push(range);
      else {
        lastMatchIndex = _list.length - lastMatchIndex;
        if (_list[lastMatchIndex] && _list[lastMatchIndex][1] === start) _list[lastMatchIndex][1] = end;
        else _list.splice(lastMatchIndex, 0, range);
      }
    }
  }

  /**
   * 新增「子范围」部分覆盖指定已知「子范围」首部，
   * 合并：新增「子范围」首部 + 已知「子范围」尾部。
   * 替换。
   */
  private mergeRangeByTop(newRangeItem: RangeItemParam, index: number) {
    let { rangeList: _list } = this;
    let temp: RangeItemParam = [newRangeItem[0], _list[index][1]];
    this.rangeList.splice(index, 1, temp);
  }

  /**
   * 新增「子范围」完全覆盖指定已知「子范围」首部，
   * 替换指定「子范围」，判断是否覆盖后续「子范围」。
   */
  private mergeRangeByInclude(newRangeItem: RangeItemParam, index: number) {
    let { rangeList: _list } = this;
    let len = 1;
    for (let i = index + 1; i < _list.length; i++) {
      if (_list[i][0] <= newRangeItem[1]) {
        len++;
        if (_list[i][1] >= newRangeItem[1]) {
          newRangeItem[1] = _list[i][1];
          break;
        }
      }
    }
    this.rangeList.splice(index, len, newRangeItem);
  }

  /**
   * 新增「子范围」部分覆盖指定已知「子范围」尾部，
   * 合并：指定「子范围」首部 + 新增「子范围」尾部。
   * 判断后续「子范围」是否于当前「子范围」范围重叠。
   * 替换。
   */
  private mergeRangeByBottom(newRangeItem: RangeItemParam, index: number) {
    let { rangeList: _list } = this;
    let len = 1;
    for (let i = index + 1; i < _list.length; i++) {
      if (_list[i][0] <= newRangeItem[1]) {
        len++;
        if (_list[i][1] >= newRangeItem[1]) {
          newRangeItem[1] = _list[i][1];
          break;
        }
      }
    }
    let temp: RangeItemParam = [_list[index][0], newRangeItem[1]];
    this.rangeList.splice(index, len, temp);
  }

  /***/
  public remove(range: RangeItemParam) {
    this.isValidRange(range);

    let [start, end] = range;
    if (start === end) return;
    let { rangeList: _list } = this;
    if (!_list.length) return;

    for (let i = 0; i < _list.length; i++) {
      let [_start, _end] = _list[i];
      if (_start > end) break;
      if (_start === start && _end === end) return this.deleteRangeItem(i);
      if (_start < start && _end > end) return this.deleteRangeMid([start, end], i);
      if (start <= _start && _start < end && end < _end) return this.deleteRangeByTop([start, end], i);
      if (start <= _start && end >= _end) return this.deleteRangeByInclude([start, end], i);
      if (start > _start && start < _end && end >= _end) return this.deleteRangeByBottom([start, end], i);
    }
  }

  /***/
  private deleteRangeByTop(newRangeItem: RangeItemParam, index: number) {
    let { rangeList: _list } = this;
    let temp: RangeItemParam = [newRangeItem[1], _list[index][1]];
    this.rangeList.splice(index, 1, temp);
  }

  /***/
  private deleteRangeItem(index: number) {
    this.rangeList.splice(index, 1);
  }

  /***/
  private deleteRangeMid(newRangeItem: RangeItemParam, index: number) {
    let { rangeList: _list } = this;
    let temp1: RangeItemParam = [_list[index][0], newRangeItem[0]];
    let temp2: RangeItemParam = [newRangeItem[1], _list[index][1]];
    this.rangeList.splice(index, 1, temp1, temp2);
  }

  /***/
  private deleteRangeByInclude(newRangeItem: RangeItemParam, index: number) {
    let { rangeList: _list } = this;
    let len = 1;
    let temp: RangeItemParam | undefined;
    for (let i = index + 1; i < _list.length; i++) {
      if (_list[i][0] < newRangeItem[1]) {
        len++;
        if (_list[i][1] !== newRangeItem[1]) temp = [newRangeItem[1], _list[i][1]];
      } else break;
    }
    if (temp) _list.splice(index, len, temp);
    else _list.splice(index, len);
  }

  /***/
  private deleteRangeByBottom(newRangeItem: RangeItemParam, index: number) {
    let { rangeList: _list } = this;
    let len = 1;
    let tempList: RangeItemParam[] = [[_list[index][0], newRangeItem[0]]];
    for (let i = index + 1; i < _list.length; i++) {
      if (_list[i][0] < newRangeItem[1]) {
        len++;
        if (_list[i][1] > newRangeItem[1]) {
          tempList.push([newRangeItem[1], _list[i][1]]);
          break;
        }
      } else break;
    }
    _list.splice(index, len, ...tempList);
  }

  public print() {
    let res = "";
    this.rangeList.forEach(item => {
      let [start, end] = item;
      for (let i = start; i < end; i++) {
        res += i + ",";
      }
    });
    res = res.slice(0, -1);
    console.log(res);
  }
}
